#ifndef xpack_net_http
#define xpack_net_http
#pragma push_macro("xuser")
#undef  xuser
#define xuser mixc::net_http::inc
#include"algo/binary_search.hpp"
#include"algo/hash.hpp"
#include"algo/sort.hpp"
#include"chrono/now_utc.hpp"
#include"chrono/datetime.hpp"
#include"define/base_type.hpp"
#include"define/classify.hpp"
#include"define/nullref.hpp"
#include"docker/hashmap.hpp"
#include"dumb/disable_copy.hpp"
#include"interface/can_alloc.hpp"
#include"interface/can_callback.hpp"
#include"interface/seqptr.hpp"
#include"lang/cxx/clone.hpp"
#include"lang/cxx/ignore_case.hpp"
#include"lang/cxx/index_of_prefix.hpp"
#include"lang/cxx/is_starts_with.hpp"
#include"lang/cxx/make_uri.hpp"
#include"lang/cxx/make_utc_date.hpp"
#include"lang/cxx/parse.hpp"
#include"lang/cxx/parse_uri.hpp"
#include"lang/cxx/parse_utc_date.hpp"
#include"lang/cxx/split.hpp"
#include"lang/cxx/strcat.hpp"
#include"lang/cxx/trim_bends.hpp"
#include"lang/cxx/trim_end.hpp"
#include"lang/cxx.hpp"
#include"macro/xexport.hpp"
#include"macro/xforward.hpp"
#include"macro/xstr.hpp"
#include"macro/xstruct.hpp"
#include"meta/has_constructor.hpp"
#include"utils/array.hpp"
#include"utils/unique_array.hpp"
#include"utils/mix_buffer.hpp"
#pragma pop_macro("xuser")

namespace mixc::net_http::origin::http{
    using care_array_t      = inc::array<inc::c08>;

    namespace method{
        constexpr char get[]                            = "GET";
        constexpr char post[]                           = "POST";
        constexpr char head[]                           = "HEAD";
        constexpr char options[]                        = "OPTIONS";
    }

    namespace head{
        constexpr char content_type[]                   = "Content-Type";
        constexpr char content_length[]                 = "Content-Length";
        constexpr char set_cookie[]                     = "Set-Cookie";
        constexpr char cookie[]                         = "Cookie";
    }

    namespace content_type{
        #define xhttp_content_type_charset "; charset=utf-8"

        constexpr char txt_html[]                       = "text/html" xhttp_content_type_charset;
        constexpr char txt_plain[]                      = "text/plain" xhttp_content_type_charset;
        constexpr char txt_xml[]                        = "text/xml" xhttp_content_type_charset;
        constexpr char img_gif[]                        = "image/gif";
        constexpr char img_jpeg[]                       = "image/jpeg";
        constexpr char img_png[]                        = "image/png";
        constexpr char app_xhtml_xml[]                  = "application/xhtml+xml" xhttp_content_type_charset;
        constexpr char app_xml[]                        = "application/xml" xhttp_content_type_charset;
        constexpr char app_atom_xml[]                   = "application/atom+xml" xhttp_content_type_charset;
        constexpr char app_json[]                       = "application/json" xhttp_content_type_charset;
        constexpr char app_pdf[]                        = "application/pdf";
        constexpr char app_octet_stream[]               = "application/octet-stream" xhttp_content_type_charset;
        constexpr char app_x_www_form_urlencoded[]      = "application/x-www-form-urlencoded" xhttp_content_type_charset;
    };

    namespace code{
        struct wrapper{
            uxx  int_value;
            char string_value[4];
            char hint[54];

            constexpr operator uxx() const {
                return int_value;
            }

            constexpr operator inc::c08() const {
                return string_value;
            }
        };

        #define xgen(NAME,CODE,HINT)     constexpr wrapper NAME = { .int_value = CODE, .string_value = #CODE, .hint = HINT };

        xgen(go_on                             , 100, "GO ON"                        );  // 继续。客户端应继续其请求
        xgen(switching_protocals               , 101, "SWITCHING PROTOCALS"          );  // 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议，例如，切换到HTTP的新版本协议
        xgen(ok                                , 200, "OK"                           );  // 请求成功。一般用于GET与POST请求
        xgen(created                           , 201, "CREATED"                      );  // 已创建。成功请求并创建了新的资源
        xgen(accepted                          , 202, "ACCEPTED"                     );  // 已接受。已经接受请求，但未处理完成
        xgen(non_auth_info                     , 203, "NON AUTH INFO"                );  // 非授权信息。请求成功。但返回的meta信息不在原始的服务器，而是一个副本
        xgen(no_content                        , 204, "NO CONTENT"                   );  // 无内容。服务器成功处理，但未返回内容。在未更新网页的情况下，可确保浏览器继续显示当前文档
        xgen(reset_content                     , 205, "RESET CONTENT"                );  // 重置内容。服务器处理成功，用户终端（例如：浏览器）应重置文档视图。可通过此返回码清除浏览器的表单域
        xgen(partial_content                   , 206, "PARTIAL CONTENT"              );  // 部分内容。服务器成功处理了部分GET请求
        xgen(multiple_choices                  , 300, "MULTIPLE CHOICES"             );  // 多种选择。请求的资源可包括多个位置，相应可返回一个资源特征与地址的列表用于用户终端（例如：浏览器）选择
        xgen(move_permanently                  , 301, "MOVE PERMANENTLY"             );  // 永久移动。请求的资源已被永久的移动到新URI，返回信息会包括新的URI，浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
        xgen(found                             , 302, "FOUND"                        );  // 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
        xgen(see_other                         , 303, "SEE OTHER"                    );  // 查看其它地址。与301类似。使用GET和POST请求查看
        xgen(not_modified                      , 304, "NOT MODIFIED"                 );  // 未修改。所请求的资源未修改，服务器返回此状态码时，不会返回任何资源。客户端通常会缓存访问过的资源，通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
        xgen(use_proxy                         , 305, "USE PROXY"                    );  // 使用代理。所请求的资源必须通过代理访问
        xgen(unused                            , 306, "UNUSED"                       );  // 已经被废弃的HTTP状态码
        xgen(temporary_redirect                , 307, "TEMPORARY REDIRECT"           );  // 临时重定向。与302类似。使用GET请求重定向
        xgen(bad_request                       , 400, "BAD REQUEST"                  );  // 客户端请求的语法错误，服务器无法理解
        xgen(unauthorized                      , 401, "UNAUTHORIZED"                 );  // 请求要求用户的身份认证
        xgen(payment_required                  , 402, "PAYMENT REQUIRED"             );  // 保留，将来使用
        xgen(forbidden                         , 403, "FORBIDDEN"                    );  // 服务器理解请求客户端的请求，但是拒绝执行此请求
        xgen(not_found                         , 404, "NOT FOUND"                    );  // 服务器无法根据客户端的请求找到资源（网页）。通过此代码，网站设计人员可设置"您所请求的资源无法找到"的个性页面
        xgen(method_not_allowed                , 405, "METHOD NOT ALLOWED"           );  // 客户端请求中的方法被禁止
        xgen(not_acceptable                    , 406, "NOT ACCEPTABLE"               );  // 服务器无法根据客户端请求的内容特性完成请求
        xgen(proxy_auth_required               , 407, "PROXY AUTH REQUIRED"          );  // 请求要求代理的身份认证，与401类似，但请求者应当使用代理进行授权
        xgen(request_timeout                   , 408, "REQUEST TIMEOUT"              );  // 服务器等待客户端发送的请求时间过长，超时
        xgen(conflict                          , 409, "CONFLICT"                     );  // 服务器完成客户端的 PUT 请求时可能返回此代码，服务器处理请求时发生了冲突
        xgen(gone                              , 410, "GONE"                         );  // 客户端请求的资源已经不存在。410不同于404，如果资源以前有现在被永久删除了可使用410代码，网站设计人员可通过301代码指定资源的新位置
        xgen(length_required                   , 411, "LENGTH REQUIRED"              );  // 服务器无法处理客户端发送的不带Content-Length的请求信息
        xgen(precondition_failed               , 412, "PRECONDITION FAILED"          );  // 客户端请求信息的先决条件错误
        xgen(request_entiry_too_large          , 413, "REQUEST ENTIRY TOO LARGE"     );  // 由于请求的实体过大，服务器无法处理，因此拒绝请求。为防止客户端的连续请求，服务器可能会关闭连接。如果只是服务器暂时无法处理，则会包含一个Retry-After的响应信息
        xgen(request_uri_too_large             , 414, "REQUEST URI TOO LARGE"        );  // 请求的URI过长（URI通常为网址），服务器无法处理
        xgen(unsupported_media_type            , 415, "UNSUPPORTED MEDIA TYPE"       );  // 服务器无法处理请求附带的媒体格式
        xgen(request_range_not_satisfiable     , 416, "REQUEST RANGE NOT SATISFIABLE");  // 客户端请求的范围无效
        xgen(expectation_failed                , 417, "EXPECTATION FAILED"           );  // 服务器无法满足Expect的请求头信息
        xgen(internel_server_error             , 500, "INTERNEL SERVER ERROR"        );  // 服务器内部错误，无法完成请求
        xgen(not_implemented                   , 501, "NOT IMPLEMENTED"              );  // 服务器不支持请求的功能，无法完成请求
        xgen(bad_gateway                       , 502, "BAD GATEWAY"                  );  // 作为网关或者代理工作的服务器尝试执行请求时，从远程服务器接收到了一个无效的响应
        xgen(service_unavailable               , 503, "SERVICE UNAVAILABLE"          );  // 由于超载或系统维护，服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
        xgen(gateway_timeout                   , 504, "GATEWAY TIMEOUT"              );  // 充当网关或代理的服务器，未及时从远端服务器获取请求
        xgen(http_version_not_supported        , 505, "HTTP VERSION NOT SUPPORTED"   );  // 服务器不支持请求的HTTP协议的版本，无法完成处理

        #undef xgen
    }

    constexpr inc::ialloc<char> reuse_cxx_memory        = nullptr;

    xstruct(
        xname(cookie),
        xprof(m_name, inc::c08),
        xprof(m_value, inc::c08),
        xprof(m_path, inc::c08),
        xprof(m_domain, inc::c08),
        xprof(m_expires, inc::datetime),
        xprof(m_is_http_only, bool),
        xprof(m_expires_mode, u08)
    )
    private:
        enum : u08{
            is_expires_date,
            is_expires_max_age,
        };
        using alloc_t = inc::ialloc<char> const &;
    public:
        xprops()
            xpubget_pubset(name, inc::c08 &);
            xpubget_pubset(value, inc::c08 &);
            xpubget_pubset(path, inc::c08 &);
            xpubget_pubset(domain, inc::c08 &);
            xpubget_pubsetx(expires, inc::datetime &){
                xr{ return xthe.m_expires; }
                xw{
                    xthe.m_expires = value;
                    xthe.m_expires_mode = is_expires_date;
                }
            };

            xpubget_pubsetx(max_age, u64){
                xr{
                    auto now_utc = inc::datetime(inc::now_utc);
                    auto diff = xthe.m_expires - now_utc;
                    return diff < 0 ? 0 : diff / 1000 /* diff 单位是 ms，转化成 s*/;
                }
                xw{
                    auto now_utc = inc::datetime(inc::now_utc);
                    xthe.m_expires->total_milisecond = now_utc->total_milisecond + value * 1000/* value 单位是秒，转化成 ms*/;
                    xthe.m_expires_mode = is_expires_max_age;
                }
            };

            xpubget_pubset(is_http_only, bool);
        $

        xprops_operator()

        #define xa_args_list       the_t const & left, the_t const & right
        #define xa_invoke          left.compare(right)
        #define xa_is_friend
        #include"macro/xgen.cmp.hpp"

    public:
        constexpr cookie() :
            m_name(),
            m_value(),
            m_path("/"),
            m_domain(),
            m_expires(),
            m_is_http_only(true),
            m_expires_mode(is_expires_date){
        }

    public:
        the_t & from(inc::c08 const & value, alloc_t alloc){
            inc::c08 line = value;
            inc::c08 cookie_token = "cookie:";

            if (line.is_starts_with(cookie_token, inc::ignore_case<char>)){
                line = line.backward(cookie_token->length);
            }

            line.trim_end("\r\n").split(';', [&](inc::cxx_split_result<char> const & result){
                // 如果没有出现 HttpOnly 字段，默认就是不开启此选项
                auto temp = cookie{}->is_http_only(false);
                auto copy = [&](auto const & value){
                    return alloc != nullptr ? value.clone(alloc) : value;
                };

                enum : uxx{
                    domain,
                    expires,
                    max_age,
                    path,
                    httponly,
                };

                constexpr inc::c08 token[] = {
                    xstr(domain=),
                    xstr(expires=),
                    xstr(max-age=),
                    xstr(path=),
                    xstr(httponly)
                };

                for(uxx i = 0, length = result->count_of_segment; i < length; i++){
                    auto item = result.segment_content_of(i).trim_bends(' ');
                    auto prefix = item.index_of_prefix(token, inc::ignore_case<char>);
                    auto i_value = prefix == not_exist ? not_exist : uxx(token[prefix]->length);

                    switch(prefix){
                    case domain:
                        temp->domain = copy(item.backward(i_value));
                        continue;
                    case expires:
                        item = item.backward(i_value);
                        temp->expires = item.parse_utc_date();
                        continue;
                    case max_age:
                        item = item.backward(i_value);
                        temp->max_age = item.parse<uxx>();
                        continue;
                    case path:
                        temp->path = copy(item.backward(i_value));
                        continue;
                    case httponly:
                        temp->is_http_only = true;
                        continue;
                    default:
                        if (i_value = item.index_of_first('='); i_value == not_exist){
                            temp->value = copy(item);
                        }
                        else{
                            temp->name = copy(inc::c08(item)->length(i_value));
                            temp->value = copy(item.backward(i_value + 1));
                        }
                        break;
                    }
                }

                xthe = temp;
            });

            return xthe;
        }

        inc::c08 make(alloc_t alloc) const {
            bool has_name = m_name->is_empty == false;
            bool has_path = m_path->is_empty == false;
            bool has_domain = m_domain->is_empty == false;
            bool has_expires = m_expires->is_valid;
            auto empty = inc::c08{};
            auto expires = inc::c08{};
            char buffer[64];

            if (not has_expires){
                ;
            }
            else if (m_expires_mode == is_expires_date){
                inc::c08{"; expires="}.clone([&](uxx length){
                    inc::c08(inc::make_utc_date, m_expires, [&](uxx date_length){
                        expires = inc::c08(buffer, length + date_length);
                        return buffer + length;
                    });
                    return buffer;
                });
            }
            else if (m_expires_mode == is_expires_max_age){
                inc::c08("; max-age=").clone([&](uxx length){
                    inc::c08(xthe->max_age, [&](uxx max_age_length){
                        expires = inc::c08(buffer, length + max_age_length);
                        return buffer + length;
                    });
                    return buffer;
                });
            }

            auto result = inc::c08{"Set-Cookie: "}.strcat(
                {
                    has_name ? m_name : empty,
                    has_name ? "=" : empty,
                    m_value,
                    has_path ? "; path=" : empty,
                    has_path ? m_path : empty,
                    has_domain ? "; domain=" : empty,
                    has_domain ? m_domain : empty,
                    has_expires ? expires : empty, 
                    m_is_http_only ? "; HttpOnly" : "",
                    "\r\n"
                },  alloc
            );

            return result;
        }

        // hashmap 要求使用此比较接口
        // 不应该以其他字段区分 cookie
        ixx compare(the_t const & right) const {
            return m_name.compare(right.m_name);
        }

        u64 hash(uxx seed = 0) const {
            return inc::hash(m_name, seed);
        }
    $

    xstruct(
        xname(header_filter),
        xpubb(inc::disable_copy),
        xprof(m_care_list, care_array_t)
    )
    public:
        xprops()
            xpubget_pubsetx(care_list, care_array_t){
                // 使用 const 限定符，不允许直接通过 care_list[index] 直接修改成员
                xrx(care_array_t const &){
                    return xthe.m_care_list;
                }

                xw{
                    xthe.m_care_list = xforward(value);
                    inc::sort::heap(xthe.m_care_list, inc::ignore_case<inc::c08>);
                }

                xprops_operator_plus(xthe.m_care_list)
            };
        $

        xprops_operator()
    public:
        header_filter() : m_care_list{} {}

        bool is_expect(inc::c08 const & field){
            return inc::binary_search::match(m_care_list, field, inc::ignore_case<inc::c08>) != not_exist;
        }
    $

    struct volatle_context{
        using kv_c08_c08    = inc::hashmap<inc::c08, inc::c08>;
        using k_cookie      = inc::hashmap<cookie>;
        using ua_t          = inc::unique_array<char>;
        using the_t         = volatle_context;

    private:
        ua_t                buffer_params_get;
    public:

        struct{
            inc::c08        method;
            inc::c08        path;
            inc::c08        http_version;
            kv_c08_c08      params_get;
            kv_c08_c08      params_post;
            kv_c08_c08      header;
            k_cookie        cookie;
            inc::c08        content;
        } in;

        struct{
            inc::c08        http_version;
            inc::c08        code;
            kv_c08_c08      params_post;
            kv_c08_c08      header;
            k_cookie        cookie;
            inc::c08        content;
        } out;

        volatle_context() :
            in{},
            out{}{

            // 默认设置
            out.http_version        = "HTTP/1.1";
            out.code                = code::internel_server_error;
        }

        bstate_t load(inc::unique_array<char> && content_memory){
            auto content            = inc::c08(content_memory.lend(), content_memory->length);
            auto parse_result       = content.parse("\v \v \v\r\n", in.method, in.path, in.http_version);
            xthe.buffer_params_get  = content_memory.hand_over();

            if (parse_result != 3u/*解析了三个参数*/ or parse_result->is_parsed_completely/* 只有这 3 个参数就结束了 */){
                return bstate_t::fail;
            }
            else{
                content             = content.backward(parse_result->index_of_error);
            }

            // 连续的换行表示解析完毕
            while(not content.is_starts_with("\r\n")){
                auto key            = inc::c08{};
                auto val            = inc::c08{};

                if (auto parse_result = content.parse("\v: \v\r\n", key, val); parse_result == 0u){
                    return bstate_t::fail;
                }
                // 是 cookie
                else if (content = content.backward(parse_result->index_of_error); key.compare(head::cookie, inc::ignore_case<char>) == 0u){
                    auto cookie     = http::cookie{}.from(val, reuse_cxx_memory);
                    in.cookie.set(cookie);
                    xdebug(im_net_http_volatle_context_load, cookie->name, cookie->value, cookie->path, cookie->domain, cookie->max_age, cookie->is_http_only);
                }
                // 普通属性
                else{
                    in.header.set(key, val);
                    xdebug(im_net_http_volatle_context_load, key, val);
                }
            }

            // 解析 get 参数
            if (auto i_param = in.path.index_of_first('?'); i_param != not_exist){
                auto params         = in.path.backward(i_param + 1);
                in.path->length     = i_param;
                xthe.parse_params(in.params_get, params);
            }

            // 跳过 \r\n
            in.content              = content.backward(2);

            xdebug(im_net_http_volatle_context_load, in.method, in.path, in.http_version);
            xdebug(im_net_http_volatle_context_load, in.content);
            return bstate_t::success;
        }

        /**
         * @brief 
         * 将 http 报文输出到回调函数中
         * @note
         * 如果使用此模块以外的其他 Content-Type 类型，需要注意 charset 设置，本模块默认只支持 utf-8
         * 可以仿照本模块 http::content_type 中的写法，新增一个自定义的名称
         * @param callback 
         */
        void output_to(inc::icallback<void(inc::c08 content)> callback){
            enum : uxx{
                max_buffer_bytes    = 4096,
            };

            using buf_t             = inc::mix_buffer<char, max_buffer_bytes>;
            using lend_t            = typename buf_t::lend_result_type;
            buf_t   buffer;
            lend_t  temp;
            auto has_params         = out.params_post->is_empty == false;
            auto new_line           = inc::c08("\r\n");
            auto alloc              = [&](uxx length){
                temp                = buffer.lend(length);
                return temp.lend();
            };

            auto push_back          = [&](){
                if (temp->is_requires_free){
                    buffer.push(temp);
                }
            };

            if (has_params){
                if (auto & type = out.header.get(head::content_type); type == inc::nullref){
                    out.header.set(head::content_type, content_type::app_x_www_form_urlencoded);
                }
                // 覆盖参数，可能存在误操作
                else if (type != content_type::app_x_www_form_urlencoded){
                    has_params      = false;
                }
            }

            out.http_version.strcat({ " ", out.code, new_line }, alloc);
            push_back();

            out.header.foreach([&](inc::c08 const & key, inc::c08 const & val){
                key.strcat({ ": ", val, new_line }, alloc);
                push_back();
            });

            out.cookie.foreach([&](cookie const & cookie){
                cookie.make(alloc);
                push_back();
            });

            auto length             = out.content->length();

            // 如果存在 content 就设置
            // 以连续的两个 new_line 作为 header 结束标志
            auto content_length     = inc::c08(head::content_length);
            auto token              = content_length.strcat({ ": 0               "/* 包含 16 个 Content-Length 占位符 */, new_line, new_line }, alloc);

            // 指向 Content-Length 占位符
            auto mem_ptr            = static_cast<char *>(token.backward(content_length->length + 2/* :\s */));
            push_back();
            
            if (auto link = inc::c08{}; has_params){
                auto offset         = buffer->length();

                out.params_post.foreach([&](inc::c08 const & key, inc::c08 const & val){
                    inc::c08(link, alloc);
                    push_back();

                    inc::c08(inc::make_uri, key, alloc);
                    push_back();

                    inc::c08("=", alloc);
                    push_back();

                    inc::c08(inc::make_uri, val, alloc);
                    push_back();

                    // 后续的 link 带上 & 
                    link            = "&";
                });

                length              = buffer->length - offset;
            }
            else{
                out.content.clone(alloc);
                push_back();
            }

            // 用 length 值填充占位符对应的内存
            inc::c08(length, [&](uxx){
                return mem_ptr;
            });

            // 输出到回调函数中
            buffer.foreach_level([&](inc::seqptr<char> const & seq){
                auto page = inc::c08(asciis(seq), seq->length);
                callback(page);
            });
        }

        void parse_params(kv_c08_c08 & hashmap, inc::c08 params){
            auto parse              = [](auto & param){
                // 由于 parse 后体积一定不会变大，所以可以使用原来地址的内存
                auto temp           = param;
                param               = param.parse_uri([&](uxx length) -> inc::seqptr<char> {
                    temp->length    = length;
                    return temp;
                });
            };

            while(true){
                auto i_split        = params.index_of_first('&');
                auto i_end          = i_split == not_exist ? params->length() : i_split;
                auto param          = inc::c08(asciis(params), i_end);
                auto i_mid          = param.index_of_first('=');
                auto key            = inc::c08(asciis(param), i_mid);
                auto val            = i_mid == not_exist ? inc::c08{} : param.backward(i_mid + 1);
                parse(key);
                parse(val);
                hashmap.set(key, val);
                xdebug(im_net_http_volatle_context_load, key, val);
                
                if (i_split == not_exist){
                    break;
                }

                // 跳到 & 后边一个字符
                params              = params.backward(i_split + 1);
            }
        }
    };
}

#endif

xexport_space(mixc::net_http::origin)
